package main

import (
	"core/encoding"
	"core/env"
	"core/graylog"
	"core/rds"
	"core/utils"
	"db"
	"fmt"
	"jigsaw/contrl"
	"net/http"
	"runtime/debug"
	"strings"
	"text/template"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/json-iterator/go"
)

type (
	HandleFunc func(c *gin.Context) (res interface{}, e contrl.Error)

	Route struct {
		Path   string
		Method string
		Func   HandleFunc
	}

	GinRoute struct {
		Path   string
		Method string
		Func   gin.HandlerFunc
	}

	RouteData struct {
		Group     *gin.RouterGroup
		Meddlers  []gin.HandlerFunc
		Path      string
		Routes    []Route
		GinRoutes []GinRoute
	}
)

var (
	adminContrl   = new(contrl.AdminCtl)
	basicContrl   = new(contrl.BasicCtl)
	userContrl    = new(contrl.UserContrl)
	rankContrl    = new(contrl.RankContrl)
	pictureContrl = new(contrl.PictureContrl)
	debugContrl   = new(contrl.DebugContrl)
)

var routeData = []RouteData{
	{
		Path: "/game", Meddlers: []gin.HandlerFunc{FilterMiddleware},
		Routes: []Route{
			{"/", http.MethodGet, basicContrl.HelloWorld},
			{"/login", http.MethodPost, basicContrl.Login},
			{"/get-conf", http.MethodGet, basicContrl.GetConf},
		},
		GinRoutes: []GinRoute{},
	},

	{
		Path: "/game", Meddlers: []gin.HandlerFunc{FilterMiddleware, AuthAdminMiddleware},
		Routes: []Route{
			{"/aof", http.MethodGet, basicContrl.Aof},
			{"/aof-today", http.MethodGet, basicContrl.AofToday},
			{"/get-user", http.MethodGet, adminContrl.GetUser},
			{"/del-user", http.MethodGet, adminContrl.DelUser},
			{"/del-cache-user", http.MethodGet, adminContrl.DelCacheUser},
			{"/add-active-user", http.MethodGet, adminContrl.AddActiveUser},
			{"/set-version", http.MethodGet, adminContrl.SetVersion},
			{"/version", http.MethodGet, adminContrl.GetVersion},
			{"/add-gift", http.MethodGet, adminContrl.AddGift},
			{"/move-open-id", http.MethodGet, adminContrl.MoveOpenId},
		},
		GinRoutes: []GinRoute{},
	},

	{
		Path: "/game", Meddlers: []gin.HandlerFunc{FilterMiddleware, AuthMiddleware},
		Routes: []Route{
			{"/basic/info", http.MethodPost, basicContrl.BasicInfo},
			{"/user/got-free-grass", http.MethodPost, userContrl.GotFreeGrass},
			{"/user/unlock", http.MethodPost, userContrl.Unlock},
			{"/user/finish", http.MethodPost, userContrl.Finish},
			{"/user/share-by-finish", http.MethodPost, userContrl.ShareByFinish},
			{"/user/save-basic-info", http.MethodPost, userContrl.SaveBasicInfo},
			{"/user/got-video-reward", http.MethodPost, userContrl.GotVideoReward},
			{"/user/got-invite-reward", http.MethodPost, userContrl.GotInviteReward},
			{"/water-rank", http.MethodGet, rankContrl.List},
			{"/upload", http.MethodPost, pictureContrl.Upload},
			{"/pic/list", http.MethodGet, pictureContrl.List},
			{"/pic/finish", http.MethodPost, pictureContrl.Finish},
			{"/pic/unlock", http.MethodPost, pictureContrl.Unlock},
			{"/pic/love", http.MethodPost, pictureContrl.Love},
			{"/pic/debug", http.MethodGet, pictureContrl.Debug},
			{"/my-home", http.MethodGet, userContrl.MyHome},
		},
		GinRoutes: []GinRoute{},
	},

	{
		Path: "/game/platform", Meddlers: []gin.HandlerFunc{AuthPlatformMiddleware},
		Routes:    []Route{},
		GinRoutes: []GinRoute{},
	},

	{
		Path: "/game/debug", Meddlers: []gin.HandlerFunc{FilterMiddleware, AuthDebugMiddleware},
		Routes: []Route{
			{"/sync", http.MethodGet, debugContrl.Sync},
		},
		GinRoutes: []GinRoute{},
	},
}

func InitRouter() (router *gin.Engine) {
	router = gin.Default()

	if !strings.Contains(string(env.RunMode), "prod") {
		config := cors.DefaultConfig()
		config.AllowAllOrigins = true
		config.AllowMethods = []string{"GET", "POST", "OPTIONS"}
		config.AllowHeaders = []string{"DNT", "X-Mx-ReqToken", "Keep-Alive", "User-Agent", "X-Requested-With", "If-Modified-Since", "Cache-Control", "Content-Type", "Authorization", "X-Token"}
		router.Use(cors.New(config))
	}
	router.Use(FilterMiddleware)
	router.Use(Recovery)

	authGroup := router.Group("/")

	for _, data := range routeData {
		// 注册路由组 && 注册路由组中间件
		data.Group = authGroup.Group(data.Path)
		for _, middlewareFunc := range data.Meddlers {
			data.Group.Use(middlewareFunc)
		}

		for _, route := range data.Routes {
			data.Group.Handle(route.Method, route.Path, genHandler(route.Func))
		}
		for _, route := range data.GinRoutes {
			data.Group.Handle(route.Method, route.Path, route.Func)
		}
	}

	return
}

func genHandler(inHandler HandleFunc) gin.HandlerFunc {
	return func(c *gin.Context) {
		beginAt := time.Now()
		res, e := inHandler(c)
		out, _ := jsoniter.Marshal(contrl.Response{
			Data:    res,
			ErrCode: e.ErrCode,
			ErrInfo: e.ErrInfo,
		})
		c.Data(e.StatusCode, "application/json", out)

		uri := utils.GetURI(c)
		graylogData := graylog.M{
			"messageId":   strings.Replace(uri, "/", "_", -1),
			"playerId":    c.Query("uid"),
			"clientIP":    utils.GetIP(c),
			"millisecond": time.Since(beginAt).Nanoseconds() / 1000000,
			"request":     fmt.Sprintf("%+v", c.Request.Form),
		}
		// graylog 打点
		if res == nil && e.ErrCode == 0 {
			graylogData["fileMark"] = graylogData["messageId"]
			graylogData["res"] = string(out)
			graylogData["stack"] = utils.Stack()
			graylog.Info("接口空返回异常", graylogData)
		}
		if e.ErrCode != 40001 && e.ErrCode != 40002 && e.ErrCode != 0 {
			graylogData["fileMark"] = graylogData["messageId"]
			graylogData["res"] = string(out)
			graylogData["stack"] = debug.Stack()
			graylogData["errcode"] = e.ErrCode
			graylogData["errInfo"] = e.ErrInfo
			graylog.Info("接口返回其他异常", graylogData)
		}
		go graylog.Info(fmt.Sprintf("[api] %s", uri), graylogData)
	}
}

func AuthMiddleware(c *gin.Context) {
	token := c.GetHeader("Authorization")
	if token == "" {
		token = c.Query("Authorization")
	}
	sign := new(contrl.Sign)
	// 调试用
	if c.GetString("adminpass") == "jigsawauth" {
		sign.Uid = utils.ToInt64(c.GetString("uid"))
		c.Set("_sign", sign)
		return
	}
	if strings.Contains(string(env.RunMode), "prod") || token != "" {
		if token == "" {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "请先登录"})
			return
		}
		err := utils.Decrypt(token, env.Conf.AppSecret2, sign)
		if err != nil {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "解析失败，请重新登录"})
			return
		}

		if sign.Uid <= 0 {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "用户不存在，请重新登录"})
			return
		}
		now := time.Now()
		if now.After(sign.ExpiredAt) {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "您已长时间离线，请重新登录"})
			return
		}
		if rds.GetCacheClient(sign.Uid).Exists(db.MakeUserKey(sign.Uid)).Val() <= 0 {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "用户不存在，请刷新后重试"})
			return
		}
	} else {
		sign.Uid = utils.ToInt64(c.GetString("uid"))
		if sign.Uid == 0 {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40001, ErrInfo: "请先登录"})
			return
		}
	}

	c.Set("_sign", sign)
}

func AuthAdminMiddleware(c *gin.Context) {
	pass := c.Query("pass")
	if pass != env.Conf.AdminPass {
		c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40002, ErrInfo: "参数错误"})
		return
	}
}

func AuthPlatformMiddleware(c *gin.Context) {
	token := c.Query("token")
	timestamp := encoding.DecodePlatform(token, env.Conf.PlatformKey)
	utils.Info(timestamp)
	if time.Now().Unix()-timestamp > 3600 {
		c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40002, ErrInfo: "参数错误"})
		return
	}
}

func AuthDebugMiddleware(c *gin.Context) {
	if strings.Contains(env.RunMode, "prod") {
		if c.GetString("adminpass") != "jigsawauth" {
			c.AbortWithStatusJSON(200, contrl.Response{Data: nil, ErrCode: 40002, ErrInfo: "参数错误。。"})
		}
	}
}

// 过滤参数，转义特殊字符
func FilterMiddleware(c *gin.Context) {
	if c.Request.Form == nil {
		c.Request.ParseForm()
	}
	if len(c.Request.Form) > 0 {
		for k, v := range c.Request.Form {
			if len(v) > 0 {
				c.Set(template.HTMLEscapeString(k), template.HTMLEscapeString(v[0]))
			}
		}
	}
}

func Recovery(c *gin.Context) {
	defer func() {
		err := recover()
		if err == nil {
			return
		}
		out, _ := jsoniter.Marshal(contrl.Response{
			Data:    nil,
			ErrCode: 50001,
			ErrInfo: "接口异常",
		})
		c.Data(200, "application/json", out)
		go graylog.Info("接口异常", graylog.M{
			"fileMark": strings.Replace(utils.GetURI(c), "/", "_", -1),
			"playerId": c.Query("uid"),
			"request":  fmt.Sprintf("%+v", c.Request.Form),
			"errInfo":  fmt.Sprintf("%+v", err),
			"stack":    utils.Stack(),
		})
	}()
	c.Next()
}
